package ltd.dujiabao.zookeeper.watcher;

import org.apache.log4j.Logger;
import org.apache.zookeeper.*;

import java.io.Closeable;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;

/**
 * @author : DuJiabao
 * @Project : zookeeper
 * @Time : 2021/5/3 11:00
 * @Desc : 配置中心，用于处理配置信息，在配置信息发生变化时，及时触发并重新处理
 * 该类必须包含IP地址，否则会出错。
 */
public class ConfigCenter implements Watcher, Closeable {
  private String ip = null;  // ip及端口
  private static Integer timeOut = 5000;  // 超时时间，毫秒为单位
  private final static CountDownLatch countDownLatch = new CountDownLatch(1);
  private final static Logger log = Logger.getLogger(ConfigCenter.class);
  private ZooKeeper zooKeeper = null;
  private DBConfig dbConfig = null;
  private final Map<String, String> config = new HashMap<>();

  public ConfigCenter() {
  }

  public ConfigCenter(String ip) throws IOException, InterruptedException {
    this(ip, timeOut);
  }

  public ConfigCenter(String ip, Integer timeOut) throws IOException, InterruptedException {
    this.ip = ip;
    ConfigCenter.timeOut = timeOut;
    initZK(ip, timeOut);
  }

  /**
   * 初始化zookeeper
   * @param ip
   * @param timeOut
   * @throws IOException
   * @throws InterruptedException
   */
  private void initZK(String ip, Integer timeOut) throws IOException, InterruptedException {
    zooKeeper = new ZooKeeper(ip, timeOut, this);
    countDownLatch.await();
  }

  /**
   * 编辑config的键值对，修改或增加某个键值对
   *
   * @param path
   * @param data
   * @return
   */
  private Map<String, String> editConfig(String path, byte[] data) {
    config.put(path, new String(data));
    return config;
  }

  /**
   * 初始化config键值对，在zookeeper中读出来
   *
   * @throws KeeperException
   * @throws InterruptedException
   */
  private void initConfig() throws KeeperException, InterruptedException {
    byte[] ip = zooKeeper.getData("/config/ip", true, null);   // 每次触发之后，会重新初始化监听器
    editConfig("/config/ip", ip);
    byte[] username = zooKeeper.getData("/config/username", true, null);
    editConfig("/config/username", username);
    byte[] password = zooKeeper.getData("/config/password", true, null);
    editConfig("/config/password", password);
  }

  /**
   * 读取zookeeper的配置值，并且放入到DBConnection中
   */
  private void initDB() {
    try {
      initConfig();
      if (dbConfig == null) {
        dbConfig = new DBConfig(config);
      } else {
        dbConfig.importConfig(config);
      }
      log.info("初始化DBConnection成功!");
    } catch (KeeperException | InterruptedException | NoSuchFieldException | IllegalAccessException e) {
      e.printStackTrace();
    }
  }


  @Override
  public void process(WatchedEvent watchedEvent) {
    // 如果是None 说明是连接方面的变化
    if (watchedEvent.getType() == Event.EventType.None) {
      switch (watchedEvent.getState()) {
        case SyncConnected:
          log.info("连接 " + ip + "成功");
          initDB();  // 初始化db
          countDownLatch.countDown();
          break;
        case Disconnected:
          log.error("连接 " + ip + "已断开");
          break;
        case Expired:
          log.error("连接 " + ip + "已超时，需要重新连接服务器端");
          try {   // 重新连接
            this.initZK(ip, timeOut);
          } catch (IOException | InterruptedException e) {
            e.printStackTrace();
          }
          break;
        case AuthFailed:
          log.error("身份验证失败");
          break;
      }
    } else {  // 如果不是连接方面的变化，那就是监视的节点发生了变化
      switch (watchedEvent.getType()) {
        case NodeDataChanged:
          log.info("配置的节点信息发生了变化，赶紧重新配置！");
          initDB();  // 因为配置发生了变化，因此需要重新初始化db
          break;
        case NodeCreated:
          log.info("添加了配置信息，目前的配置信息是");
          break;
      }
    }
  }

  @Override
  public void close() throws IOException {
    try {
      zooKeeper.close();
      log.info("zooKeeper已关闭");
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}
